﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace IkSan.ExtJs4EF4
{
    public static class MappedSerializer
    {
        public static object Serialize(this object input, string mapString)
        {
            var map = GenerateMap(mapString);
            return input.Serialize(map);
        }

        static object Serialize(this object input, Map map)
        {
            if (input == null)
                return null;

            var type = input.GetType();
            var result = new Dictionary<string, object>();

            foreach (var member in map.Members)
            {
                if (member.Name == "*")
                {
                    var valueProperties = type.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));
                    foreach (var p in valueProperties)
                    {
                        if (p.PropertyType.IsSubclassOf(typeof(IEnumerable)))
                        {
                            continue;
                        }
                        Process(input, type, new Member { Name = p.Name }, result);
                    }
                }
                else
                {
                    Process(input, type, member, result);
                }
            }
            return result;
        }

        private static void Process(object input, Type type, Member member, Dictionary<string, object> result)
        {
            var property = type.GetProperty(member.Name);
            var value = property.GetValue(input, null);

            //if (value is DateTime)
            //{
            //    result.Add(member.Name, value.Serialize("Day,Month,Year,Hour,Minute,Second,Ticks"));
            //    return;
            //}

            if (value is IEnumerable && member.Map != null)
            {
                var values = (from object o in (IEnumerable)value select o.Serialize(member.Map));
                result.Add(member.Name, values);
                return;
            }

            result.Add(member.Name, member.Map != null ? value.Serialize(member.Map) : value);
        }

        static Map GenerateMap(string mapString)
        {
            var rootMap = new Map();
            var currentMap = rootMap;

            var lastIndex = 0;
            string sub;
            int index;
            for (index = 0; index < mapString.Length; index++)
            {
                var c = mapString[index];
                switch (c)
                {
                    case ',':
                        {
                            sub = mapString.Substring(lastIndex, index - lastIndex);
                            currentMap.Members.Add(new Member { Name = sub });
                            lastIndex = index + 1;
                            break;
                        }
                    case '>':
                        {
                            var newMap = new Map { ParentMap = currentMap };
                            sub = mapString.Substring(lastIndex, index - lastIndex);
                            currentMap.Members.Add(new Member { Name = sub, Map = newMap });
                            currentMap = newMap;
                            lastIndex = index + 1;
                            break;
                        }
                    case '<':
                        {
                            sub = mapString.Substring(lastIndex, index - lastIndex);
                            if (!string.IsNullOrEmpty(sub))
                            {
                                currentMap.Members.Add(new Member { Name = sub });
                            }
                            currentMap = currentMap.ParentMap;
                            lastIndex = index + 1;
                            break;
                        }
                }
            }
            sub = mapString.Substring(lastIndex, index - lastIndex);
            if (!string.IsNullOrEmpty(sub))
            {
                currentMap.Members.Add(new Member { Name = sub });
            }

            return rootMap;
        }

        public class Map
        {
            public Map()
            {
                Members = new List<Member>();
            }

            public Map ParentMap { get; set; }
            public List<Member> Members { get; set; }
        }

        public class Member
        {
            public string Name { get; set; }
            public Map Map { get; set; }
        }


    }
}